<?php

namespace IYUU\sdk\CoolQ;

use Psr\SimpleCache\InvalidArgumentException;
use think\facade\Cache;

/**
 * 上下文
 * Class Context
 * @package IYUU\sdk\CoolQ
 */
class Context
{
    /**
     * 缓存KEY:私聊Session
     */
    const CacheKeyPrivateSession = 'Context:SessionPrivate:{self_id}:{user_id}';

    /**
     * 缓存Key:群聊Session
     */
    const CacheKeyGroupSession = 'Context:SessionGroup:{self_id}:{group_id}:{user_id}';

    /**
     * Session中的KEY：用来存放关键字匹配到的模块规则
     * - RuleKeywordModel模型数据（cq_rule_keyword数据表）
     */
    const moduleRuleKeywordSession = 'MODULE_RULE_KEYWORD';

    /**
     * Session中的KEY：用来存放上下文路由锁定信息
     */
    const moduleInContextSession = 'MODULE_IN_CONTEXT';

    /**
     * Session中的KEY：用来存放用户数据
     */
    const moduleUserSession = 'UserSession';

    /**
     * 缓存Key:群聊消息
     * - 管理群消息复读
     */
    const CacheKeyGroupMessage = 'Context:GroupMessage:{self_id}:{group_id}:msg_{hash}';

    /**
     * OneBoot机器人上报的完整事件数据包
     * - （只读）
     * @var array
     */
    private $Yaf_CoolQ_Event;

    /**
     * 被重载的Session数据保存在此
     * - 当php终止执行时，会把此处的数据，保存到缓存之中
     * @var array
     */
    private $session = [];

    /**
     * 构造函数
     * Context constructor.
     * @param mixed $Yaf_CoolQ_Event
     */
    public function __construct(array $Yaf_CoolQ_Event = [])
    {
        // 初始化成员
        $this->Yaf_CoolQ_Event = $Yaf_CoolQ_Event;

        // 初始化Session
        $this->initSessionByCache($Yaf_CoolQ_Event);

        // 注册一个会在php中止时执行的函数，保存Session到缓存中
        register_shutdown_function(array($this, 'saveSessionToCache'));
    }

    /**
     * 获取OneBoot机器人上报的完整事件数据包中的参数 (支持.分隔符)
     * @param string|int $key
     * @param mixed $default
     * @return mixed|null
     */
    public function get($key = null, $default = null)
    {
        if ($key === null) {
            return $this->Yaf_CoolQ_Event;
        }
        $key_array = \explode('.', $key);
        $value = $this->Yaf_CoolQ_Event;
        foreach ($key_array as $index) {
            if (!isset($value[$index])) {
                return $default;
            }
            $value = $value[$index];
        }
        return $value;
    }

    /**
     * 从缓存读取数据，初始化Session
     * @param array $Yaf_CoolQ_Event
     */
    protected function initSessionByCache(array $Yaf_CoolQ_Event)
    {
        if ($cache_key = static::getSessionCacheKey($Yaf_CoolQ_Event)) {
            $session = static::getCache($cache_key);
            if ($session && is_array($session)) {
                $this->session = $session;
            }
        }
    }

    /**
     * 保存Session数据到缓存
     * @access public
     */
    public function saveSessionToCache()
    {
        if ($cache_key = static::getSessionCacheKey($this->Yaf_CoolQ_Event)) {
            static::setCache($cache_key, $this->session, 1200);
        }
    }

    /**
     * 计算Session的缓存key
     * @param array $event
     * @return string
     */
    protected static function getSessionCacheKey(array $event): string
    {
        $cache_key = '';
        if (isset($event['post_type']) && ($event['post_type'] === 'message') && !empty($event['message_type'])) {
            if ($event['message_type'] === 'private') {
                // 私聊Session的缓存key
                $replace = [
                    '{self_id}' => $event['self_id'],
                    '{user_id}' => $event['user_id']
                ];
                $cache_key = strtr(static::CacheKeyPrivateSession, $replace);
            } else {
                // 群聊Session的缓存key
                $replace = [
                    '{self_id}' => $event['self_id'],
                    '{group_id}' => $event['group_id'],
                    '{user_id}' => $event['user_id']
                ];
                $cache_key = strtr(static::CacheKeyGroupSession, $replace);
            }
        }
        return $cache_key;
    }

    /**
     * 获取缓存
     * @param $key
     * @param null $default
     * @return mixed
     */
    public static function getCache($key, $default = null)
    {
        try {
            return Cache::get($key);
        } catch (InvalidArgumentException $ex) {
        }
        return $default;
    }

    /**
     * 写入缓存
     * @param $key
     * @param $value
     * @param int $ttl
     * @return void
     */
    public static function setCache($key, $value, int $ttl = 600)
    {
        try {
            Cache::set($key, $value, $ttl);
        } catch (InvalidArgumentException $ex) {
        }
    }

    /**
     * 使用缓存，对KEY计数
     * @param $key
     * @param int $ttl
     * @param int $default
     * @return int
     * @throws InvalidArgumentException
     */
    public static function incCache($key, int $ttl = 60, int $default = 1): int
    {
        $num = Cache::get($key);
        if ($num) {
            ++$num;
        } else {
            $num = $default;
        }
        Cache::set($key, $num, $ttl);
        return $num;
    }

    /**
     * 获取Session
     * @param string|null $key
     * @param mixed $default
     * @return array|mixed|null
     */
    public function getSession(string $key = null, $default = null)
    {
        if ($key === null) {
            return $this->session;
        }
        $key_array = \explode('.', $key);
        $value = $this->session;
        foreach ($key_array as $index) {
            if (!array_key_exists($index, $value)) {
                return $default;
            }
            $value = $value[$index];
        }
        return $value;
    }

    /**
     * 设置Session
     * @param string $key
     * @param mixed $value
     * @return $this
     */
    public function setSession(string $key, $value): Context
    {
        $this->session[$key] = $value;
        return $this;
    }

    /**
     * 移除指定Session
     * @param string $key
     * @return Context
     */
    public function unsetSession(string $key): Context
    {
        unset($this->session[$key]);
        return $this;
    }

    /**
     * 读取不可访问属性的值时，__get() 会被调用
     *
     * @param string $name 参数名字
     * @return mixed
     */
    public function __get(string $name)
    {
        return $this->getSession($name, null);
    }

    /**
     * 在给不可访问属性赋值时，__set() 会被调用
     *
     * @param string $name 参数名字
     * @param mixed $value 参数解析后的值
     */
    public function __set(string $name, $value)
    {
        $this->setSession($name, $value);
    }

    /**
     * 当对不可访问属性调用 isset() 或 empty() 时，__isset() 会被调用
     *
     * @param string|int $name
     * @return bool
     */
    public function __isset(string $name): bool
    {
        return isset($this->session[$name]);
    }

    /**
     * 当对不可访问属性调用 unset() 时，__unset() 会被调用
     *
     * @param string|int $name
     */
    public function __unset(string $name): void
    {
        unset($this->session[$name]);
    }
}
